package com.google.zxing.journeyapps.barcodescanner;

import com.google.zxing.BarcodeFormat;
import com.google.zxing.DecodeHintType;
import com.google.zxing.ResourceTable;
import com.google.zxing.ResultPoint;
import com.google.zxing.MultiFormatReader;
import com.google.zxing.client.ohos.DecodeFormatManager;
import com.google.zxing.client.ohos.DecodeHintManager;
import com.google.zxing.client.ohos.Intents;
import com.google.zxing.journeyapps.barcodescanner.camera.CameraParametersCallback;
import com.google.zxing.journeyapps.barcodescanner.camera.CameraSettings;
import ohos.aafwk.content.Intent;
import ohos.agp.components.Attr;
import ohos.agp.components.AttrSet;
import ohos.agp.components.Component;
import ohos.agp.components.StackLayout;
import ohos.agp.components.LayoutScatter;
import ohos.agp.components.Text;
import ohos.agp.utils.TextTool;
import ohos.app.Context;
import ohos.multimodalinput.event.KeyEvent;

import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Set;

/**
 * Encapsulates BarcodeView, ViewfinderView and status text.
 *
 * To customize the UI, use BarcodeView and ViewfinderView directly.
 */
public class DecoratedBarcodeView extends StackLayout implements Component.KeyEventListener {
    private BarcodeView barcodeView;
    private ViewfinderView viewFinder;
    private Text statusView;

    /**
     * The instance of @link TorchListener to send events callback.
     */
    private TorchListener torchListener;

    private class WrappedCallback implements BarcodeCallback {
        private BarcodeCallback delegate;

        public WrappedCallback(BarcodeCallback delegate) {
            this.delegate = delegate;
        }

        @Override
        public void barcodeResult(BarcodeResult result) {
            delegate.barcodeResult(result);
        }

        @Override
        public void possibleResultPoints(List<ResultPoint> resultPoints) {
            for (ResultPoint point : resultPoints) {
                viewFinder.addPossibleResultPoint(point);
            }
            delegate.possibleResultPoints(resultPoints);
        }
    }

    public DecoratedBarcodeView(Context context) {
        super(context);
        initialize(context);
    }

    public DecoratedBarcodeView(Context context, AttrSet attrs) {
        super(context, attrs);
        initialize(context,attrs);
    }

    public DecoratedBarcodeView(Context context, AttrSet attrs, String defStyleName) {
        super(context, attrs, defStyleName);
        initialize(context,attrs);
    }



    /**
     * Initialize the view with the xml configuration based on styleable attributes.
     *
     * @param context
     * @param attrs The attributes to use on view.
     */
    private void initialize(Context context, AttrSet attrs) {
        int scannerLayout = ResourceTable.Layout_zxing_barcode_scanner;
        if (attrs != null) {
            Optional<Attr> optional = attrs.getAttr("zxing_scanner_layout");
            if (optional.isPresent()) {
                String value = optional.get().getStringValue();
                if (value != null && !value.trim().isEmpty()) {
                    String replace = value.replace("$layout:", "");
                    scannerLayout = Integer.parseInt(replace);
                }
            }
        }
        LayoutScatter.getInstance(context).parse(scannerLayout, this, true);
        barcodeView = (BarcodeView) findComponentById(ResourceTable.Id_zxing_barcode_surface);

        if (barcodeView == null) {
            throw new IllegalArgumentException(
                "There is no a com.journeyapps.barcodescanner.BarcodeView on provided layout " +
                "with the id \"zxing_barcode_surface\".");
        }

        // Pass on any preview-related attributes
        barcodeView.initializeAttributes(attrs);

        viewFinder = (ViewfinderView) findComponentById(ResourceTable.Id_zxing_viewfinder_view);

        if (viewFinder == null) {
            throw new IllegalArgumentException(
                "There is no a com.journeyapps.barcodescanner.ViewfinderView on provided layout " +
                "with the id \"zxing_viewfinder_view\".");
        }

        viewFinder.setCameraPreview(barcodeView);

        // statusView is optional
        statusView = (Text) findComponentById(ResourceTable.Id_zxing_status_view);
    }

    /**
     * Initialize with no custom attributes set.
     */
    private void initialize(Context context) {
        initialize(context, null);
    }

    /**
     * Convenience method to initialize camera id, decode formats and prompt message from an intent.
     *
     * @param intent the intent, as generated by IntentIntegrator
     */
    public void initializeFromIntent(Intent intent) {
        // Scan the formats the intent requested, and return the result to the calling activity.
        Set<BarcodeFormat> decodeFormats = DecodeFormatManager.parseDecodeFormats(intent);
        Map<DecodeHintType, Object> decodeHints = DecodeHintManager.parseDecodeHints(intent);

        CameraSettings settings = new CameraSettings();

        if (intent.hasParameter(Intents.Scan.CAMERA_ID)) {
            String cameraId = intent.getStringParam(Intents.Scan.CAMERA_ID);
            if (!TextTool.isNullOrEmpty(cameraId)) {
                settings.setRequestedCameraId(cameraId);
            }
        }

        if (intent.hasParameter(Intents.Scan.TORCH_ENABLED)) {
            if (intent.getBooleanParam(Intents.Scan.TORCH_ENABLED, false)) {
                this.setTorchOn();
            }
        }

        String customPromptMessage = intent.getStringParam(Intents.Scan.PROMPT_MESSAGE);
        if (customPromptMessage != null) {
            setStatusText(customPromptMessage);
        }

        // Check what type of scan. Default: normal scan
        int scanType = intent.getIntParam(Intents.Scan.SCAN_TYPE, 0);

        String characterSet = intent.getStringParam(Intents.Scan.CHARACTER_SET);

        MultiFormatReader reader = new MultiFormatReader();
        reader.setHints(decodeHints);

        barcodeView.setCameraSettings(settings);
        barcodeView.setDecoderFactory(new DefaultDecoderFactory(decodeFormats, decodeHints, characterSet, scanType));
    }

    public void setCameraSettings(CameraSettings cameraSettings) {
        barcodeView.setCameraSettings(cameraSettings);
    }

    public void setDecoderFactory(DecoderFactory decoderFactory) {
        barcodeView.setDecoderFactory(decoderFactory);
    }

    public DecoderFactory getDecoderFactory() {
        return barcodeView.getDecoderFactory();
    }

    public CameraSettings getCameraSettings() {
        return barcodeView.getCameraSettings();
    }

    public void setStatusText(String text) {
        // statusView is optional when using a custom layout
        if (statusView != null) {
            statusView.setText(text);
        }
    }

    /**
     * @see BarcodeView#pause()
     */
    public void pause() {
        barcodeView.pause();
    }

    /**
     * @see BarcodeView#pauseAndWait()
     */
    public void pauseAndWait() {
        barcodeView.pauseAndWait();
    }

    /**
     * @see BarcodeView#resume()
     */
    public void resume() {
        barcodeView.resume();
    }

    public BarcodeView getBarcodeView() {
        return (BarcodeView) findComponentById(ResourceTable.Id_zxing_barcode_surface);
    }

    public ViewfinderView getViewFinder() {
        return viewFinder;
    }

    public Text getStatusView() {
        return statusView;
    }

    /**
     * @see BarcodeView#decodeSingle(BarcodeCallback)
     */
    public void decodeSingle(BarcodeCallback callback) {
        barcodeView.decodeSingle(new WrappedCallback(callback));
    }

    /**
     * @see BarcodeView#decodeContinuous(BarcodeCallback)
     */
    public void decodeContinuous(BarcodeCallback callback) {
        barcodeView.decodeContinuous(new WrappedCallback(callback));
    }

    /**
     * Turn on the device's flashlight.
     */
    public void setTorchOn() {
        barcodeView.setTorch(true);

        if (torchListener != null) {
            torchListener.onTorchOn();
        }
    }

    /**
     * Turn off the device's flashlight.
     */
    public void setTorchOff() {
        barcodeView.setTorch(false);

        if (torchListener != null) {
            torchListener.onTorchOff();
        }
    }

    /**
     * Changes the settings for Camera.
     * Must be called after {@link #resume()}.
     *
     * @param callback {@link CameraParametersCallback}
     */
    public void changeCameraParameters(CameraParametersCallback callback) {
        barcodeView.changeCameraParameters(callback);
    }

    /**
     * Handles focus, camera, volume up and volume down keys.
     *
     * Note that this view is not usually focused, so the Activity should call this directly.
     */

    @Override
    public boolean onKeyEvent(Component component, KeyEvent keyEvent) {

        switch (keyEvent.getKeyCode()) {
            case KeyEvent.KEY_FOCUS:
            case KeyEvent.KEY_CAMERA:
                // Handle these events so they don't launch the Camera app
                return true;
            // Use volume up/down to turn on light
            case KeyEvent.KEY_VOLUME_DOWN:
                setTorchOff();
                return true;
            case KeyEvent.KEY_VOLUME_UP:
                setTorchOn();
                return true;
        }
        return false;
    }

    public void setTorchListener(TorchListener listener) {
        this.torchListener = listener;
    }

    /**
     * The Listener to torch/fflashlight events (turn on, turn off).
     */
    public interface TorchListener {

        void onTorchOn();

        void onTorchOff();
    }
}
